﻿using System;
using System.Collections.Generic;
using System.Reflection;
using FluentlyXml.Core.BuiltInSerializers;

namespace FluentlyXml.Core.Serialization
{
	internal class TypeSerializationConfig
	{
		private PropertySerializerDictionary _propertySerializers;
		private bool? _thisConfigAppliesToAllDerivedTypes;

		public Type AppliesToType { get; private set; }

		public TypeSerializationConfig(Type appliesToType)
		{
			AppliesToType = appliesToType;

			InitializePropertyBehaviors(appliesToType);
		}

		private void InitializePropertyBehaviors(Type appliesToType)
		{
			_propertySerializers = new PropertySerializerDictionary();
			foreach (var property in appliesToType.GetPropertiesForSerialization())
			{
				if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof (IList<>))
				{
					_propertySerializers[property] = SerializationBehaviors.AsArray;
				}
				else
				{
					//TODO: Clean this up.
					_propertySerializers[property] = (ctx, obj, prop) =>
														{
															return SerializationBehaviors.AsElement(ctx, obj, prop.Name, o => prop.GetValue(o, null));
														};
				}
			}
		}

		public void SerializePropertyAsArray(PropertyInfo property)
		{
			_propertySerializers[property] = SerializationBehaviors.AsArray;
		}

		public void IgnoreProperty(PropertyInfo property)
		{
			_propertySerializers[property] = SerializationBehaviors.Ignore;
		}

		public void MergeWith(TypeSerializationConfig config)
		{
			_thisConfigAppliesToAllDerivedTypes = config._thisConfigAppliesToAllDerivedTypes ?? _thisConfigAppliesToAllDerivedTypes;

			_propertySerializers.MergeWith(config._propertySerializers);
		}

		public void SerializePropertyAsElement(PropertyInfo property, string elementName)
		{
			_propertySerializers[property] = SerializationBehaviors.AsElementNamed(elementName);
		}

		public void SerializePropertyAsElementUsing(PropertyInfo property, string elementName, Func<object, object> converter)
		{
			//TODO: encapsulate or chain? 
			_propertySerializers[property] = (ctx, obj, prop) =>
			                               	{
			                               		return SerializationBehaviors.AsElement(ctx, obj, elementName, converter);
			                               	};
		}

		public void SerializeAllDerivedTypesUsingThisConfig()
		{
			_thisConfigAppliesToAllDerivedTypes = true;
		}

		public ITypeSerializer BuildSerializer()
		{
			CustomSerializer serializer;

			if (_thisConfigAppliesToAllDerivedTypes == true)
			{
				serializer = new CustomSerializerForAllDerivedTypes(AppliesToType);
			}
			else
			{
				serializer = new CustomSerializer(AppliesToType);				
			}

			foreach (var propSerializers in _propertySerializers)
			{
				serializer.AddPropertySerializer(propSerializers.Key, propSerializers.Value);
			}

			return serializer;
		}
	}
}